﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace Nirvana
{
    public sealed class SkinnedMeshBinder 
    {
        private struct SkinnedMeshBinderData
        {
            public SkinnedMeshRenderer skinned;
            public Transform[] bones;
            public Transform root;
        }
        private static Logger logger = LogSystem.GetLogger("SkinnedMeshBinder");
        private List<SkinnedMeshBinderData> skinlist = new List<SkinnedMeshBinderData>();
        private Dictionary<string, Transform> skinMap;

        public void AddRenderer(SkinnedMeshRenderer skin, Transform[] bones, Transform root)
        {
            SkinnedMeshBinderData _data = default(SkinnedMeshBinderData);
            _data.skinned = skin;
            _data.bones = bones;
            _data.root = root;
            this.skinlist.Add(_data);
        }

        public void AddRenderer(SkinnedMeshRenderer skin, Transform root)
        {
            SkinnedMeshBinderData _data = default(SkinnedMeshBinderData);
            _data.skinned = skin;
            _data.bones = skin.bones;
            _data.root = root;
            this.skinlist.Add(_data);
        }

        public void Bind(Transform rootbone)
        {
            if(this.skinMap == null)
            {
                this.skinMap = new Dictionary<string, Transform>();
            }

            this.skinMap.Clear();
            this.FindChilds(rootbone);
            foreach(var skindata in this.skinlist)
            {
                SkinnedMeshRenderer skin = skindata.skinned;
                Transform[] bones = new Transform[skindata.bones.Length];
                for(int i = 0; i < skindata.bones.Length; i++)
                {
                    Transform bone = skindata.bones[i];
                    Transform newbone = null;
                    if (!this.skinMap.TryGetValue(bone.name,out newbone))
                    {
                        logger.LogWarning("Can not find the bone: " + bone.name);
                    }
                    bones[i] = newbone;
                }
                skin.bones = bones;
                skin.rootBone = rootbone;

            }
        }

        public void CombineSkinnedMesh(GameObject target)
        {
            int subMeshCount = 0;
            foreach(var skin in this.skinlist)
            {
                subMeshCount += skin.skinned.sharedMesh.subMeshCount;
            }
            List<Transform> bones = new List<Transform>(this.skinMap.Values);
            List<BoneWeight> boneWeights = new List<BoneWeight>();
            List<Texture2D> allTexture = new List<Texture2D>();
            List<CombineInstance> combineInstances = new List<CombineInstance>();
            int[] vertexList = new int[subMeshCount];

            Material material = null;
            int i = 0;
            while (i < this.skinlist.Count)
            {
                SkinnedMeshBinderData skindata = this.skinlist[i];
                SkinnedMeshRenderer skin = skindata.skinned;
                foreach(var weight in skin.sharedMesh.boneWeights)
                {
                    BoneWeight boneweight = weight;
                    boneweight.boneIndex0 = bones.IndexOf(skin.bones[weight.boneIndex0]);
                    boneweight.boneIndex1 = bones.IndexOf(skin.bones[weight.boneIndex1]);
                    boneweight.boneIndex2 = bones.IndexOf(skin.bones[weight.boneIndex2]);
                    boneweight.boneIndex3 = bones.IndexOf(skin.bones[weight.boneIndex3]);
                    boneWeights.Add(boneweight);
                }

                if(skin.sharedMaterial.mainTexture != null)
                {
                    if (material == null) material = skin.sharedMaterial;
                    allTexture.Add(skin.sharedMaterial.mainTexture as Texture2D);
                }

                CombineInstance combineInstance = default(CombineInstance);
                combineInstance.mesh = skin.sharedMesh;
                vertexList[i] = combineInstance.mesh.vertexCount;
                if(skindata.root == null)
                {
                    combineInstance.transform = skin.transform.localToWorldMatrix;
                }
                else
                {
                    Transform bone;
                    if (this.skinMap.TryGetValue(skindata.root.name, out bone))
                    {
                        combineInstance.transform = Matrix4x4.TRS(bone.position, skin.transform.rotation, Vector3.one);
                    }
                    else
                    {
                        logger.LogError("The bone {0} is not existed.", skindata.root.name);
                    }
                }
                
                combineInstances.Add(combineInstance);
                if (Application.isPlaying)
                {
                    UnityEngine.Object.Destroy(skin.gameObject);
                }
                else
                {
                    UnityEngine.Object.DestroyImmediate(skin.gameObject);
                }
                i++;
            }

            Matrix4x4[] bindposes = new Matrix4x4[bones.Count];
            for(int j = 0; j < bones.Count; i++)
            {
                bindposes[j] = bones[j].worldToLocalMatrix;
            }

            SkinnedMeshRenderer skinnedMeshRenderer = target.AddComponent<SkinnedMeshRenderer>();
            skinnedMeshRenderer.sharedMesh = new Mesh();
            skinnedMeshRenderer.sharedMesh.name = "Combined Skin Mesh";
            skinnedMeshRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, true);
            skinnedMeshRenderer.sharedMesh.hideFlags = HideFlags.DontSave;

            Texture2D newTexture = null;
            Rect[] newRect = null;
            Singleton<TextureCombiner>.Instance.Combine(allTexture.ToArray(), out newTexture, out newRect);
            Vector2[] uvs = skinnedMeshRenderer.sharedMesh.uv;
            Vector2[] newUvs = new Vector2[uvs.Length];

            int index1 = 0, index2 = 0;

            for(int j = 0; j < newUvs.Length; j++)
            {
                if (j >= vertexList[index1] + index2)
                {
                    index2 += vertexList[index1];
                    index1++;
                }
                Rect _rect = newRect[index2];
                newUvs[j].x = uvs[j].x * _rect.width + _rect.x;
                newUvs[j].y = uvs[j].y * _rect.height + _rect.y;
            }

            Material mat = new Material(material);
            mat.mainTexture = newTexture;
            skinnedMeshRenderer.sharedMesh.uv = newUvs;
            skinnedMeshRenderer.sharedMaterial = mat;
            skinnedMeshRenderer.bones = bones.ToArray();
            skinnedMeshRenderer.sharedMesh.boneWeights = boneWeights.ToArray();
            skinnedMeshRenderer.sharedMesh.bindposes = bindposes;
            skinnedMeshRenderer.sharedMesh.RecalculateBounds();

        }



        private void FindChilds(Transform root)
        {
            var trans = root.GetComponentsInChildren<Transform>(true);
            this.skinMap.Add(root.name, root);
            foreach(var t in trans)
            {
                if (this.skinMap.ContainsKey(t.name))
                    this.skinMap[t.name] = t;
                else
                    this.skinMap.Add(t.name, t);
            }
        }
    }
}

